package com.geekydoo.commands;

import generated.TaskType;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.logging.Logger;

import javax.xml.datatype.XMLGregorianCalendar;

import com.geekydoo.InputOutputHandler;
import com.geekydoo.InputValidation;
import com.geekydoo.XmlCalendarUtil;

/**
 * The purpose of this class is to support displaying tasks that match
 * user-specified criteria.
 * 
 * @author Wai Yau
 */
public class DisplayCommand implements Command {

	private static Logger logger = Logger.getLogger("geekydoo");

	// The list of all task objects managed by the program
	private List<TaskType> taskList;

	// Informational messages
	private static final String INFO_EMPTY_LIST = "Task list is empty.";
	private static final String INFO_TASK_NOT_FOUND = "No task found for the given criteria.";

	// Error messages caused by invalid user input
	private static final String ERROR_CRITERIA = "Valid criteria are date, time, category";
	private static final String ERROR_DATE = "Invalid date.";
	private static final String ERROR_TIME = "Invalid time.";

	// Allowed user inputs for which criterion to display by
	private static final String INPUT_DATE = "((?i)date|(?i)day)";
	private static final String INPUT_TIME = "((?i)time)";
	private static final String INPUT_CATEGORY = "((?i)category|(?i)categories)";

	// Display output labels
	private static final String LABEL_ID = "Task id: ";
	private static final String LABEL_TITLE = "Title: ";
	private static final String LABEL_DATE = "Date: ";
	private static final String LABEL_TIME = "Time: ";
	private static final String LABEL_CATEGORY = "Categories: ";
	private static final String LABEL_DESCRIPTION = "Description: ";
	private static final String LABEL_DOCUMENTLINK = "Document Link: ";

	// Prompts printed by the display command
	private static final String PROMPT_DISPLAY_CRITERIA = "Display by: (blank == show all): ";
	private static final String PROMPT_DATE = "Enter date (DD-MM-YYYY or DD-MM): ";
	private static final String PROMPT_TIME = "Enter time (HH:MM): ";
	private static final String PROMPT_CATEGORY = "Enter categories (comma delimited): ";

	// Separators
	private static final String SEPARATOR_DATE = "-";
	private static final String SEPARATOR_TIME = ":";
	private static final String SEPARATOR_TASK = "--------------------------------------------------------------------";

	// Integer used to indicate no matching tasks were found
	private static final int MARKER_TASK_NOT_FOUND = 0;

	// Descriptive name for the empty string
	private static final String EMPTY_STRING = "";

	// Regular expression for the delimiter in a string representing multiple
	// categories
	private static final String REGEX_CATEGORY_DIVIDER = "\\s*,\\s*";

	public void execute(List<TaskType> tasks) {
		taskList = tasks;
		if (taskList.size() == 0) {
			logger.info("Task list is empty so we won't display anything");
			InputOutputHandler.printToConsole(INFO_EMPTY_LIST, true, false);
			return;
		}

		String criteria = InputOutputHandler.getInputFromConsole(PROMPT_DISPLAY_CRITERIA, true);
		while (!InputValidation.isValidCriteria(criteria)) {
			InputOutputHandler.printToConsole(ERROR_CRITERIA, true, false);
			criteria = InputOutputHandler.getInputFromConsole(PROMPT_DISPLAY_CRITERIA, true);
		}

		processCriteria(criteria);

	}

	/**
	 * This method executes display logic specific to the criteria argument.
	 * 
	 * @param criteria The criteria to display by
	 */
	private void processCriteria(String criteria) {
		if (criteria.matches(INPUT_DATE)) {
			processDateResponse();
		}
		else if (criteria.matches(INPUT_TIME)) {
			processTimeResponse();
		}
		else if (criteria.matches(INPUT_CATEGORY)) {
			processCategoryResponse();
		}
		else if (criteria.equals(EMPTY_STRING)) {
			printAllTasks();
		}
	}

	/**
	 * This method prompts the user for a date and then displays tasks falling
	 * on that date.
	 */
	private void processDateResponse() {
		String dateString = InputOutputHandler.getInputFromConsole(PROMPT_DATE, true);
		while (!InputValidation.isValidDateString(dateString)) {
			InputOutputHandler.printToConsole(ERROR_DATE, true, false);
			dateString = InputOutputHandler.getInputFromConsole(PROMPT_DATE, true);
		}
		dateString = XmlCalendarUtil.standardizeDateString(dateString);
		logger.info("The user wants to display all tasks matching date: " + dateString);
		printTasks(INPUT_DATE, dateString);
	}

	/**
	 * This method prompts the user for a time and then displays tasks occurring
	 * at that time.
	 */
	private void processTimeResponse() {
		String timeString = InputOutputHandler.getInputFromConsole(PROMPT_TIME, true);
		while (!InputValidation.isValidTimeString(timeString)) {
			InputOutputHandler.printToConsole(ERROR_TIME, true, false);
			timeString = InputOutputHandler.getInputFromConsole(PROMPT_TIME, true);
		}

		timeString = XmlCalendarUtil.standardizeTimeString(timeString);
		logger.info("The user wants to display all tasks matching time: " + timeString);
		printTasks(INPUT_TIME, timeString);
	}

	/**
	 * This method prompts the user for categories and then displays tasks
	 * matching those categories.
	 */
	private void processCategoryResponse() {
		String category = InputOutputHandler.getInputFromConsole(PROMPT_CATEGORY, true);
		logger.info("The user wants to display all tasks matching categories: " + category);
		printTasks(INPUT_CATEGORY, category);
	}

	/**
	 * This method prints out tasks whose value for the fieldType argument
	 * (date, time, etc.) matches the user's query, which is given in the
	 * fieldInput argument.
	 * 
	 * @param fieldType The criteria type
	 * @param fieldInput User input for criteria
	 */
	private void printTasks(String fieldType, String fieldInput) {
		int counter = MARKER_TASK_NOT_FOUND;
		InputOutputHandler.printToConsole(SEPARATOR_TASK, true, false);
		for (int i = 0; i < taskList.size(); i++) {
			TaskType aTask = taskList.get(i);
			String fieldValue = returnAppropriateFieldFromTask(fieldType, aTask);
			counter = printMatchingTasksInfo(fieldType, fieldInput, fieldValue, aTask, counter);
		}
		if (counter == MARKER_TASK_NOT_FOUND) {
			InputOutputHandler.printToConsole(INFO_TASK_NOT_FOUND, true, false);
		}
	}

	/**
	 * This method returns the value from the aTask argument corresponding with
	 * the fieldType argument. (Example: returns 10:30 when fieldType is
	 * INPUT_TIME and aTask has its time set to 10:30)
	 * 
	 * @param fieldType The criteria type
	 * @param aTask The task to get the value from
	 * @return the appropriate value from the task
	 */
	private String returnAppropriateFieldFromTask(String fieldType, TaskType aTask) {
		String fieldValue = EMPTY_STRING;
		if (fieldType.equals(INPUT_DATE)) {
			fieldValue =
					aTask.getDate().getDay() + SEPARATOR_DATE + aTask.getDate().getMonth() + SEPARATOR_DATE
							+ aTask.getDate().getYear();
		}
		else if (fieldType.equals(INPUT_TIME)) {
			if (aTask.getTime() == null) {
				fieldValue = EMPTY_STRING;
			}
			else {
				fieldValue = aTask.getTime().getHour() + SEPARATOR_TIME + aTask.getTime().getMinute();
			}
		}
		else if (fieldType.equals(INPUT_CATEGORY)) {
			fieldValue = aTask.getCategory();
		}
		return fieldValue;
	}

	/**
	 * This method prints a task if it matched the criteria and keeps track of
	 * how many matches were found.
	 * 
	 * @param fieldType The criteria type we are trying to match
	 * @param fieldInput The value the user is searching for for the given
	 *            criteria
	 * @param fieldValue The aTask argument's value for the given criteria
	 * @param aTask The task to test for a match
	 * @param counter A count of the total number of matches found
	 * @return a count of the number of matching tasks
	 */
	private int printMatchingTasksInfo(String fieldType, String fieldInput, String fieldValue, TaskType aTask,
			int counter) {
		if (fieldType.equals(INPUT_CATEGORY)) {
			ArrayList<String> taskCategoryList =
					new ArrayList<String>(Arrays.asList(fieldValue.split(REGEX_CATEGORY_DIVIDER)));
			ArrayList<String> inputCategoryList =
					new ArrayList<String>(Arrays.asList(fieldInput.split(REGEX_CATEGORY_DIVIDER)));
			Iterator<String> inputCategoryIterator = inputCategoryList.iterator();

			int categoriesMatched = 0;
			int totalCategories = inputCategoryList.size();
			while (inputCategoryIterator.hasNext()) {
				String currentInputCategory = inputCategoryIterator.next();
				if (taskCategoryList.contains(currentInputCategory)) {
					categoriesMatched++;
				}
			}

			if (categoriesMatched == totalCategories) {
				printTaskDetails(aTask);
				counter++;
			}
		}
		else if (fieldInput.equalsIgnoreCase(fieldValue)) {
			printTaskDetails(aTask);
			counter++;
		}
		return counter;
	}

	/**
	 * This method prints all tasks from a given task list.
	 */
	private void printAllTasks() {
		logger.info("The user wants to display all tasks");
		InputOutputHandler.printToConsole(SEPARATOR_TASK, true, false);
		for (int i = 0; i < taskList.size(); i++) {
			TaskType aTask = taskList.get(i);
			printTaskDetails(aTask);
		}
	}

	/**
	 * This method prints out the details for a task.
	 * 
	 * @param aTask The task
	 */
	private void printTaskDetails(TaskType aTask) {
		InputOutputHandler.printToConsole(LABEL_ID + aTask.getId(), true, false);
		InputOutputHandler.printToConsole(LABEL_TITLE + aTask.getTitle(), true, true);
		XMLGregorianCalendar theDate = aTask.getDate();
		int day = theDate.getDay();
		int month = theDate.getMonth();
		int year = theDate.getYear();
		InputOutputHandler.printToConsole(LABEL_DATE + day + SEPARATOR_DATE + month + SEPARATOR_DATE + year, true,
				false);
		if (aTask.getTime() != null) {
			String time = aTask.getTime().toString();
			String[] timeParts = time.split(SEPARATOR_TIME);
			String hour = timeParts[0];
			String minute = timeParts[1];
			InputOutputHandler.printToConsole(LABEL_TIME + hour + SEPARATOR_TIME + minute, true, false);
		}
		if (!aTask.getCategory().equals(EMPTY_STRING)) {
			InputOutputHandler.printToConsole(LABEL_CATEGORY + aTask.getCategory(), true, true);
		}
		if (!aTask.getDescription().equals(EMPTY_STRING)) {
			InputOutputHandler.printToConsole(LABEL_DESCRIPTION + aTask.getDescription(), true, true);
		}
		List<String> linkList = aTask.getDocumentLinks();
		ListIterator<String> linkIterator = linkList.listIterator();
		String link = null;
		while (linkIterator.hasNext()) {
			link = linkIterator.next();
			if (link != null && !link.equals(EMPTY_STRING)) {
				InputOutputHandler.printToConsole(LABEL_DOCUMENTLINK + link, true, true);
			}
		}
		InputOutputHandler.printToConsole(SEPARATOR_TASK, true, false);
	}

	public boolean isListModified() {
		return false;
	}

	public List<TaskType> getPreExecutionList() {
		return taskList;
	}

}
